<?php
/**
 * custom logger class
 * User: functions
 * Date: 2016/1/28
 * Time: 11:34
 */

namespace Tools\Log;

use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;

class Logger implements LoggerInterface
{

    /**
     * Detailed debug information
     */
    const DEBUG = 100;

    /**
     * Interesting events
     *
     * Examples: User logs in, SQL logs.
     */
    const INFO = 200;

    /**
     * Uncommon events
     */
    const NOTICE = 250;

    /**
     * Exceptional occurrences that are not errors
     *
     * Examples: Use of deprecated APIs, poor use of an API,
     * undesirable things that are not necessarily wrong.
     */
    const WARNING = 300;

    /**
     * Runtime errors
     */
    const ERROR = 400;

    /**
     * Critical conditions
     *
     * Example: Application component unavailable, unexpected exception.
     */
    const CRITICAL = 500;

    /**
     * Action must be taken immediately
     *
     * Example: Entire website down, database unavailable, etc.
     * This should trigger the SMS alerts and wake you up.
     */
    const ALERT = 550;

    /**
     * Urgent alert.
     */
    const EMERGENCY = 600;

    /**
     * Monolog API version
     *
     * This is only bumped when API breaks are done and should
     * follow the major version of the library
     *
     * @var int
     */
    const API = 1;

    /**
     * Logging levels from syslog protocol defined in RFC 5424
     *
     * @var array $levels Logging levels
     */
    protected static $levels = array(
        100 => 'DEBUG',
        200 => 'INFO',
        250 => 'NOTICE',
        300 => 'WARNING',
        400 => 'ERROR',
        500 => 'CRITICAL',
        550 => 'ALERT',
        600 => 'EMERGENCY',
    );

    protected $directories;
    protected $handlers;
    protected $requestId;

    public function __construct()
    {

    }

    /**
     * System is unusable.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function emergency($message, array $context = array())
    {
        $this->log(static::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function alert($message, array $context = array())
    {
        $this->log(static::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function critical($message, array $context = array())
    {
        $this->log(static::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function error($message, array $context = array())
    {
        $this->log(static::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function warning($message, array $context = array())
    {
        $this->log(static::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function notice($message, array $context = array())
    {
        $this->log(static::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function info($message, array $context = array())
    {
        $this->log(static::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function debug($message, array $context = array())
    {
        $this->log(static::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param string $message
     * @param array $context
     * @return null
     */
    public function log($level, $message, array $context = array())
    {
        $this->addRecord($level, $message, $context);
    }

    /**
     * Gets the name of the logging level.
     *
     * @param  integer $level
     * @return string
     */
    public static function getLevelName($level)
    {
        if(!isset(static::$levels[$level])) {
            throw new InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . implode(', ', array_keys(static::$levels)));
        }

        return static::$levels[$level];
    }

    /**
     * set log save directories
     * @param $directories
     * @return $this
     */
    public function setDirectories($directories)
    {
        $this->directories = $directories;
        return $this;
    }

    /**
     * get log save directories
     * @return mixed
     */
    public function getDirectories()
    {
        if(!$this->directories || !is_dir($this->directories)) {
            throw new InvalidArgumentException(" Log Directories is not invalid ");
        }
        return $this->directories;
    }

    /**
     * Adds a log record.
     *
     * @param  integer $level The logging level
     * @param  string $message The log message
     * @param  array $context The log context
     * @return Boolean Whether the record has been processed
     */
    protected function addRecord($level, $message, array $context = array())
    {
        $levelName = static::getLevelName($level);
        $directories = $this->getDirectories();
        $fileDirectories = $directories . DIRECTORY_SEPARATOR . $levelName . DIRECTORY_SEPARATOR;
        $fileName = date("Y-m-d") . ".log";
        if(!file_exists($fileDirectories)) {
            $mkdirPath = mkdir($fileDirectories, 0777, true);
            if(!$mkdirPath) {
                throw new \RuntimeException(" Unable to create log directory. ");
            }
        }

        $filePath = $fileDirectories . DIRECTORY_SEPARATOR . $fileName;
        $format = "[Date:%s][RequestId:".$this->getRequestId()."][%s]\n";
        if(!empty($context)) {
            $format .= " [Extra data]%s \n";
        }

        if($level === static::ERROR) {
            $trace = $this->getTraceDetails();
            $format .= " [TRACE]\n%s\n";
            $string = sprintf($format, date("Y-m-d H:i:s"), $message, var_export($context, true), $trace);
        } else {
            $string = sprintf($format, date("Y-m-d H:i:s"), $message, var_export($context, true));
        }
        file_put_contents($filePath, $string, FILE_APPEND);
    }

    /**
     * get traces details
     * @return array
     */
    protected function getTraceDetails()
    {
        $traces = debug_backtrace();
        $details = "";
        if($traces) {
            foreach($traces as $k => $trace) {
                $details .= "[{$k}] {$trace['file']}({$trace['line']}),Function:{$trace['function']}()\n";
            }
        }
        return $details;
    }

    public function getRequestId()
    {   
        if(!$this->requestId) {
            $this->requestId = md5(time().rand(1,100));
        }
        return $this->requestId;
    }

    public function setRequestId($requestId)
    {
        if(!empty($requestId)) {
            $this->requestId = $requestId;
        }
    }
}